package com.gitee.wsl.common.ui.ext

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.unit.LayoutDirection
import com.gitee.wsl.ext.math.PI_RAD
import com.gitee.wsl.ext.math.piRad
import com.gitee.wsl.ext.number.half
import com.gitee.wsl.math.degreesToRadians
import com.gitee.wsl.mathematics.geometry.shape.Rectangle
import com.gitee.wsl.mathematics.geometry.shape.toRect
import kotlin.math.*

/**
 * Return true if a rect overlap another rect.
 */
fun Rect.overlap(r: Rect):Boolean = left < r.right && right > r.left && top < r.bottom && bottom > r.top

fun List<Rect>.union(): Rect? = fold<Rect, Rect?>(null) { acc, rect ->
    if (acc != null) {
        Rect(
            min(rect.left, acc.left),
            min(rect.top, acc.top),
            max(rect.right, acc.right),
            max(rect.bottom, acc.bottom)
        )
    } else {
        rect
    }
}


/**
 * Translates the rectangle by the given matrix transformation.
 * This shifts the rectangle's position based on the matrix's translation values.
 *
 * @param matrix The matrix that contains translation values.
 * @return A new rectangle that is translated by the matrix.
 */
fun Rect.translate(matrix: Matrix): Rect = this.translate(
    matrix.values[12],
    matrix.values[13],
)


fun Rect.union(other:Rect): Rect? = listOf(this,other).union()

fun Rect.toMutableRect() = Rectangle(this)

/**
 * Update this Rect to enclose itself and the [x,y] coordinate. There is no
 * check to see that this rectangle is non-empty.
 *
 * @param x The x coordinate of the point to add to the rectangle
 * @param y The y coordinate of the point to add to the rectangle
 */
fun Rect.union(x: Float, y: Float):Rect {
    val mutableRect = this.toMutableRect()
    if (x < left) {
        mutableRect.left = x
    } else if (x > right) {
        mutableRect.right = x
    }
    if (y < top) {
        mutableRect.top = y
    } else if (y > bottom) {
        mutableRect.bottom = y
    }
    return mutableRect.toRect()
}


operator fun Rect.times(scale: Float): Rect =
    Rect(
        topLeft = topLeft * scale,
        bottomRight = bottomRight * scale,
    )

operator fun Rect.div(scale: Float): Rect =
    Rect(
        topLeft = topLeft / scale,
        bottomRight = bottomRight / scale,
    )

fun Rect.topStart(layoutDirection: LayoutDirection): Offset = when (layoutDirection) {
    LayoutDirection.Ltr -> topLeft
    LayoutDirection.Rtl -> topRight
}

fun Rect.topEnd(layoutDirection: LayoutDirection): Offset = when (layoutDirection) {
    LayoutDirection.Ltr -> topRight
    LayoutDirection.Rtl -> topLeft
}

fun Rect.centerStart(layoutDirection: LayoutDirection): Offset = when (layoutDirection) {
    LayoutDirection.Ltr -> centerLeft
    LayoutDirection.Rtl -> centerRight
}

fun Rect.centerEnd(layoutDirection: LayoutDirection): Offset = when (layoutDirection) {
    LayoutDirection.Ltr -> centerRight
    LayoutDirection.Rtl -> centerLeft
}

fun Rect.bottomStart(layoutDirection: LayoutDirection): Offset = when (layoutDirection) {
    LayoutDirection.Ltr -> bottomLeft
    LayoutDirection.Rtl -> bottomRight
}

fun Rect.bottomEnd(layoutDirection: LayoutDirection): Offset = when (layoutDirection) {
    LayoutDirection.Ltr -> bottomRight
    LayoutDirection.Rtl -> bottomLeft
}

fun Rect.rotate(degrees: Float): Rect =
    when {
        degrees % PI_RAD == 0f -> this
        degrees % 0.5f.piRad == 0f -> {
            if (width != height) {
                Rect(
                    left = center.x - height.half,
                    top = center.y - width.half,
                    right = center.x + height.half,
                    bottom = center.y + width.half,
                )
            } else {
                this
            }
        }

        else -> {
            val alpha = degrees.degreesToRadians()
            val sinAlpha = sin(alpha)
            val cosAlpha = cos(alpha)

            val newWidth = (abs(width * cosAlpha) + abs(height * sinAlpha)).toFloat()
            val newHeight = (abs(width * sinAlpha) + abs(height * cosAlpha)).toFloat()

            Rect(
                left = center.x - newWidth.half,
                top = center.y - newHeight.half,
                right = center.x + newWidth.half,
                bottom = center.y + newHeight.half,
            )
        }
    }

fun Rect.getStart(isLtr: Boolean): Float = if (isLtr) left else right

fun Rect.getEnd(isLtr: Boolean): Float = if (isLtr) right else left

fun Rect.extendBy(
    left: Float = 0f,
    top: Float = 0f,
    right: Float = 0f,
    bottom: Float = 0f,
): Rect =
    Rect(
        left = this.left - left,
        top = this.top - top,
        right = this.right + right,
        bottom = this.bottom + bottom,
    )


/**
 * Flip this rect horizontally or vertically within a given container
 *
 */
fun Rect.flip(spaceSize: Size, vertical: Boolean = false): Rect {
    return if (!vertical) {
        Rect(
            left = spaceSize.width - right,
            top = top,
            right = spaceSize.width - left,
            bottom = bottom
        )
    } else {
        Rect(
            left = left,
            top = spaceSize.height - bottom,
            right = right,
            bottom = spaceSize.height - top
        )
    }
}

/**
 * Rotate the space by [rotation] degrees, and then return the rotated Rect
 *
 */
fun Rect.rotateInSpace(spaceSize: Size, rotation: Int): Rect {
    require(rotation % 90 == 0) { "rotation must be a multiple of 90, rotation: $rotation" }
    return when ((rotation % 360).let { if (it < 0) 360 + it else it }) {
        90 -> {
            Rect(
                left = spaceSize.height - this.bottom,
                top = this.left,
                right = spaceSize.height - this.top,
                bottom = this.right
            )
        }

        180 -> {
            Rect(
                left = spaceSize.width - this.right,
                top = spaceSize.height - this.bottom,
                right = spaceSize.width - this.left,
                bottom = spaceSize.height - this.top,
            )
        }

        270 -> {
            Rect(
                left = this.top,
                top = spaceSize.width - this.right,
                right = this.bottom,
                bottom = spaceSize.width - this.left,
            )
        }

        else -> this
    }
}

/**
 * Scales up the rect by the given scale.
 */
fun Rect.scale(scale: Float):Rect {
    return if (scale != 1.0f) {
        Rect(
        left = (left * scale + 0.5f),
        top = (top * scale + 0.5f),
        right = (right * scale + 0.5f),
        bottom = (bottom * scale + 0.5f)
        )
    } else this
}

/**
 * If rectangles a and b intersect, return true and set this rectangle to
 * that intersection, otherwise return false and do not change this
 * rectangle. No check is performed to see if either rectangle is empty.
 * To just test for intersection, use intersects()
 *
 * @param a The first rectangle being intersected with
 * @param b The second rectangle being intersected with
 * @return true iff the two specified rectangles intersect. If they do, set
 * this rectangle to that intersection. If they do not, return
 * false and do not change this rectangle.
 */
fun Rect.setIntersect(a: Rect, b: Rect): Rect {
    if (a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom) {
        val mutableRect =  toMutableRect()
        mutableRect.left = max(a.left, b.left)
        mutableRect.top = max(a.top, b.top)
        mutableRect.right = min(a.right, b.right)
        mutableRect.bottom = min(a.bottom, b.bottom)
        return mutableRect.toRect()
    }
    return this
}

fun Rect.contains( x: Float, y: Float, extraXSpace: Float): Boolean {
    return (x >= left - extraXSpace && x <= right + extraXSpace && y >= top && y <= bottom)
}

fun Rect.almostContains(x: Float, y: Float, extraSpace: Float): Boolean {
    return (x >= left - extraSpace && x <= right + extraSpace && y >= top - extraSpace && y <= bottom + extraSpace)
}

/**
 * Checks if this rectangle contains another rectangle.
 * A rectangle contains another if it fully encloses the other rectangle.
 *
 * @param other The rectangle to check.
 * @return `true` if this rectangle contains the other, `false` otherwise.
 */
fun Rect.contains(other: Rect): Boolean = (this.left <= other.left) &&
        (this.right >= other.right) &&
        (this.top <= other.top) &&
        (this.bottom >= other.bottom)
